package de.hijacksoft.isle;

import android.view.MotionEvent;
import de.hijacksoft.isle.adapters.ConnectionStatusListener;
import static de.hijacksoft.isle.classes.Variables.*;

import java.util.ArrayList;
import java.util.List;

import net.londatiga.android.ActionItem;
import net.londatiga.android.QuickAction;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.net.ConnectivityManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.speech.RecognizerIntent;
import android.text.method.DigitsKeyListener;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ArrayAdapter;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.RatingBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import de.hijacksoft.isle.adapters.BarcooDBAdapter;
import de.hijacksoft.isle.classes.Category;
import de.hijacksoft.isle.adapters.DBAdapter;
import de.hijacksoft.isle.classes.Item;
import de.hijacksoft.isle.adapters.MainExpandableListAdapter;
import de.hijacksoft.isle.classes.Price;
import de.hijacksoft.isle.adapters.ProgressListener;
import de.hijacksoft.isle.adapters.SyncAdapter;
import de.hijacksoft.isle.classes.Recipe;
import de.hijacksoft.isle.classes.Variables;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Collections;

public class ListActivity extends Activity implements ConnectionStatusListener {

    // Constants
    private static final String TAG = "ListActivity";
    public static final int MODE_MENU = 0;
    public static final int MODE_LIST = 1;
    // ---
    public static final int QUESTION_MODE_AMOUNT = 0;
    public static final int QUESTION_MODE_LASTBOUGHT = 1;
    // ---
    private ArrayList<Item> infos = new ArrayList<Item>();
    private DBAdapter adapter;
    private MainExpandableListAdapter myAdapter = new MainExpandableListAdapter(this, R.layout.item, infos, adapter);
    private double totalPrice = 0;
    // Views
    private Button autogenerate, scan, clear, add, sync, speech;
    private ExpandableListView list;
    private TextView price;
    LinearLayout listempty;
    private ProgressDialog progressD;
    private HorizontalScrollView hsv;
    // Generator
    private static ListGenerator generator;
    // used to not display dialogs when showing a tutorial
    private boolean juststarted = true;
    // used to change item categories
    private Category itemCat, newCat;

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.listactivity);

        initDB();

        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);

        // make the screen stay turned on if set in the preferences
        if (preferences.getBoolean(Variables.PREFERENCE_KEEPSCREENON, false)) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }

        // show a tutorial if set in the preferences and disable it for next time
        if (preferences.getBoolean(PREFERENCE_TUTORIAL, true)) {
            preferences.edit().putBoolean(PREFERENCE_TUTORIAL, false).commit();
            juststarted = false;
            startActivityForResult(new Intent(this, Tutorial.class), TUTORIAL);
        }

        // disable screen orientation changing because we dont want the activity to refresh
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);

        createUI();
        initList();

        updateGenerator();

    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {

        switch (keyCode) {

            case KeyEvent.KEYCODE_SEARCH: // start the voice recognition activity or if that doesnt work a barcode scanner

                try {
                    Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
                    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
                    startActivityForResult(intent, VOICE_RECOGNITION_REQUEST_CODE);
                    progressD = ProgressDialog.show(ListActivity.this, getString(R.string.app_name), getString(R.string.busy), true, true);
                } catch (Exception ex) {
                    Log.e(TAG, "No SpeechInput " + ex);
                    // start the barcode scanner if voice recognition doesnt work
                    try {
                        Intent intent = new Intent("com.google.zxing.client.android.SCAN");
                        startActivityForResult(intent, BARCODE_ACTIVITY_REQUEST_CODE);
                    } catch (Exception e) {
                        showError(getString(R.string.noscanner));
                    }

                }
                break;
        }

        return super.onKeyDown(keyCode, event);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        adapter.close();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(Variables.PREFERENCE_KEEPSCREENON, false)) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }
        updateGenerator();
        checkClearButton();
        myAdapter.notifyDataSetChanged();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, final Intent data) {

        switch (requestCode) {
            case TUTORIAL:
                initList();
                break;
            case BARCODE_ACTIVITY_REQUEST_CODE:

                switch (resultCode) {
                    case RESULT_OK:
                        long barcode = 0;
                        try {
                            barcode = Long.parseLong(data.getStringExtra("SCAN_RESULT"));
                        } catch (NumberFormatException e) {
                            Log.i(TAG, "failed scanning: " + e.getMessage());
                            Toast.makeText(this, this.getString(R.string.wrongbarcode),
                                    Toast.LENGTH_SHORT).show();
                            return;
                        }
                        Item localitem = adapter.getItemByBarcode(barcode);

                        if (localitem != null) {
                            adapter.addToMainList(localitem);
                            infos.add(localitem);
                        } else { // try to get online data if the item isnt in the database already
                            if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
                                    Variables.PREFERENCE_USEONLINE, true)) {
                                // check if internet connection is available
                                ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
                                if (cm.getActiveNetworkInfo() != null
                                        && cm.getActiveNetworkInfo().isConnected()) {
                                    DownloadInfoTask dt = new DownloadInfoTask();
                                    dt.execute(barcode);
                                } else {
                                    showError(getString(R.string.checkinternet));
                                }
                            } else {
                                // show a "notfound" toast if online use is turned off and the item isnt in the database
                                Toast.makeText(this, this.getString(R.string.notfound), Toast.LENGTH_SHORT).show();
                            }
                        }
                        break;
                }
                initList();
                break;

            case ITEM_RETURNED: // add all items from the list of items returned
                switch (resultCode) {
                    case RESULT_OK:
                        for (int i = 0; i < data.getExtras().size(); i++) {
                            try {
                                Item item = adapter.getItemById(data.getExtras().getInt("id_" + i));
                                adapter.addToMainList(item);
                            } catch (Exception e) {
                                // prevents nullpointers and other stuff. doesnt want to be logged
                            }
                        }
                        break;
                }
                initList();
                break;

            case VOICE_RECOGNITION_REQUEST_CODE:
                switch (resultCode) {
                    case RESULT_OK:
                        new AsyncTask() {

                            private ArrayList<Item> items;

                            @Override
                            protected Object doInBackground(Object... arg0) {
                                // get all possible matches of the spoken words
                                ArrayList<String> matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
                                Log.v(TAG, "Spoken: " + matches.toString());
                                // need all items here to check which fits best

                                items = getItemsFromStrings(matches);

                                return null;
                            }

                            @Override
                            protected void onPostExecute(Object arg0) {
                                if (items.size() > 1) {
                                    generator.addItemListQuestion(items, QUESTION_MODE_AMOUNT);
                                }
                                if (items.size() == 1) {
                                    // show a question whether to add the found item
                                    AlertDialog.Builder dB = new AlertDialog.Builder(ListActivity.this);
                                    dB.setTitle(getString(R.string.add));
                                    dB.setMessage(items.get(0).amount
                                            + (items.get(0).unit == 'm' ? getString(R.string.unit_ml) : items.get(0).unit == 'l' ? getString(R.string.unit_l) : items.get(0).unit == 'g' ? getString(R.string.unit_g) : items.get(0).unit == 'k' ? getString(R.string.unit_kg) : getString(R.string.unit_pc))
                                            + " " + items.get(0).name + " ?");
                                    dB.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {

                                        public void onClick(DialogInterface dialog, int id) {
                                            adapter.addToMainList(items.get(0));
                                            infos.add(items.get(0));
                                            initList();
                                            myAdapter.notifyDataSetChanged();
                                        }
                                    });
                                    dB.setNegativeButton(getString(R.string.no), null);
                                    AlertDialog d = dB.create();
                                    d.show();
                                }
                                progressD.dismiss();
                            }
                        }.execute();
                        break;
                    default:
                        if (progressD != null) {
                            progressD.dismiss();
                        }
                        break;

                }
                break;

            default:
                initList();
                break;
        }
    }

    /**
     * parses a list of Strings and returns the most likely fittable items
     * @param strings list of Strings containing names (and amounts)
     * @return the list of Items most likely to fit the Strings
     */
    private ArrayList<Item> getItemsFromStrings(ArrayList<String> strings) {

        ArrayList<Item> allItems = adapter.getAllItems();

        // seperate all possible Strings into a list
        ArrayList<ArrayList<Item>> itemDescriptions = new ArrayList<ArrayList<Item>>();

        // seperate all single Strings by the "and" String
        for (String str : strings) {
            ArrayList<Item> singleItems = new ArrayList<Item>();
            itemDescriptions.add(singleItems);
            String[] infos = str.split(" " + getString(R.string.and) + " ");
            for (String info : infos) {
                Item item = new Item();
                item.name = info;
                item.amount = Double.parseDouble("0" + info.replaceAll("\\D", ""));
                singleItems.add(item);
            }
        }

        // the whole similarity for each list of Strings (that were seperated by the "and" String)
        double[] wholeSimilarity = new double[itemDescriptions.size()];

        // compare all items with the name saved in the newly created item list
        for (int x = 0; x < itemDescriptions.size(); x++) {
            wholeSimilarity[x] = 0;
            for (int y = 0; y < itemDescriptions.get(x).size(); y++) {
                ArrayList<Item> items = itemDescriptions.get(x);
                for (int index = 0; index < items.size(); index++) {
                    int[] similarity = new int[allItems.size()];
                    int finalSimilarity = 0;
                    int bestChoice = -1;
                    for (int i = 0; i < allItems.size(); i++) {
                        similarity[i] = (int) (similarity(items.get(index).name.toLowerCase(), allItems.get(i).name.toLowerCase()) * 100);
                        if (bestChoice == -1 || similarity[i] > similarity[bestChoice]) {
                            bestChoice = i;
                            finalSimilarity = similarity[i];
                        }
                    }
                    items.get(index).name = allItems.get(bestChoice).name;
                    items.get(index).id = allItems.get(bestChoice).id;
                    if (items.get(index).amount <= 0) {
                        items.get(index).amount = allItems.get(bestChoice).amount;
                    }
                    items.get(index).category = allItems.get(bestChoice).category;
                    items.get(index).unit = allItems.get(bestChoice).unit;
                    wholeSimilarity[x] += finalSimilarity;
                }
            }
        }

        int resultNumber = -1;

        for (int i = 0; i < wholeSimilarity.length; i++) {
            if (resultNumber == -1 || wholeSimilarity[i] > wholeSimilarity[resultNumber]) {
                resultNumber = i;
            }
        }

        return itemDescriptions.get(resultNumber);
    }

    /**
     * shows a menu to choose different things from like preferences
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        MenuInflater mi = new MenuInflater(this);
        mi.inflate(R.layout.menu, menu);
        return true;
    }

    @Override
    public boolean onMenuItemSelected(int featureId, MenuItem item) {
        switch (item.getItemId()) {
            case (R.id.menu_options):
                startActivity(new Intent(this, Preferences.class));
                break;
            case (R.id.menu_sync):
                SynchronizeTask task = new SynchronizeTask();
                task.addConnectionStatusListener(this);
                task.execute();
                break;
        }
        return true;
    }

    /**
     * disable/enable clear button as needed
     */
    private void checkClearButton() {
        if (myAdapter.getChildrenCount() > 0) {
            clear.setEnabled(true);
            listempty.setVisibility(View.GONE);
            list.setVisibility(View.VISIBLE);
        } else {
            clear.setEnabled(false);
            listempty.setVisibility(View.VISIBLE);
            list.setVisibility(View.GONE);
        }
    }

    /**
     * update the shown price of the shopping list
     */
    private void updatePrice() {
        if (totalPrice != 0 && infos.size() > 0) {
            price.setVisibility(View.VISIBLE);
            price.setText(getString(R.string.price) + ": " + new Price(getApplicationContext(), totalPrice).toString());
        } else {
            price.setVisibility(View.GONE);
        }
    }

    /**
     * autogenerates a shopping list with items and recipes as set in the preferences
     */
    private void autogenerate() {
        // only execute auto generate if set in the preferences
        boolean auto = PreferenceManager.getDefaultSharedPreferences(ListActivity.this).getBoolean(Variables.PREFERENCE_AUTOGENERATE, false);
        if (auto && myAdapter.getChildrenCount() == 0 && generator.canGenerate()) {
            boolean recipes = PreferenceManager.getDefaultSharedPreferences(ListActivity.this).getBoolean(Variables.PREFERENCE_USERECIPES, false);
            int recipesToAdd = 0;
            // only add recipes if set in the preferences
            if (recipes) {
                String recipesToAdd_pref = PreferenceManager.getDefaultSharedPreferences(ListActivity.this).getString(Variables.PREFERENCE_RECIPECOUNT, "5");
                recipesToAdd = Integer.parseInt(recipesToAdd_pref);
            }
            if (recipesToAdd > 0 || generator.canItems) {
                boolean genItems = PreferenceManager.getDefaultSharedPreferences(ListActivity.this).getBoolean(Variables.PREFERENCE_GENERATEITEMS, true);

                generator.generate(recipesToAdd, genItems, false);
                Toast.makeText(this, this.getString(R.string.autogenerate), Toast.LENGTH_SHORT).show();
            }
        }
    }

    // update the automatic list generator or create it if it doesnt exist
    private void updateGenerator() {
        generator = new ListGenerator();
        if (!generator.canGenerate()) {
            this.autogenerate.setEnabled(false);
        } else {
            this.autogenerate.setEnabled(true);
        }

    }

    private void createUI() {

        autogenerate = (Button) findViewById(R.id.listactivity_btn_autogenerate);
        add = (Button) findViewById(R.id.listactivity_btn_add);
        clear = (Button) findViewById(R.id.listactivity_btn_clear);
        sync = (Button) findViewById(R.id.listactivity_btn_synchronize);
        scan = (Button) findViewById(R.id.listactivity_btn_scan);
        speech = (Button) findViewById(R.id.listactivity_btn_speech);
        list = (ExpandableListView) findViewById(R.id.listactivity_list_items);
        price = (TextView) findViewById(R.id.listactivity_textview_price);
        hsv = (HorizontalScrollView) findViewById(R.id.listactivity_hscrollview_buttonscrollview);
        listempty = (LinearLayout) findViewById(R.id.listactivity_view_listempty);

        final QuickAction addQuickAction = new QuickAction(this);

        // Build Add QuickAction
        // Item ActionItem
        ActionItem addItem = new ActionItem();

        addItem.setTitle(getString(R.string.item));
        addItem.setIcon(getResources().getDrawable(R.drawable.qa_item));
        addQuickAction.addActionItem(addItem);

        // Item ActionItem
        ActionItem addRecipe = new ActionItem();

        addRecipe.setTitle(getString(R.string.recipe));
        addRecipe.setIcon(getResources().getDrawable(R.drawable.qa_recipe));
        addQuickAction.addActionItem(addRecipe);

        addQuickAction.setOnActionItemClickListener(new QuickAction.OnActionItemClickListener() {

            @Override
            public void onItemClick(int pos) {
                switch (pos) {
                    case 0:
                        Intent i = new Intent(ListActivity.this, ItemSelectActivity.class);
                        i.putExtra(KEY_MODE, MODE_MAIN); // start itemselectactivity in main mode
                        startActivityForResult(i, ITEM_RETURNED);
                        break;
                    case 1:
                        startActivityForResult(new Intent(ListActivity.this, RecipeSelectActivity.class), RECIPE_RETURNED);
                        break;
                }
            }
        });

        hsv.setScrollbarFadingEnabled(false);

        hsv.setOnTouchListener(new OnTouchListener() {

            public boolean onTouch(View arg0, MotionEvent arg1) {
                ((Button) findViewById(R.id.listactivity_arrow_left)).getBackground().setAlpha((int) (((((double) hsv.getScrollX() + 1d) / 71d) * 255d)));
                ((Button) findViewById(R.id.listactivity_arrow_right)).getBackground().setAlpha((int) ((1 - (((double) hsv.getScrollX() + 1d) / 71d) * 255d)));
                return false;
            }
        });

        hsv.setOnFocusChangeListener(new OnFocusChangeListener() {

            public void onFocusChange(View arg0, boolean arg1) {
                ((Button) findViewById(R.id.listactivity_arrow_left)).getBackground().setAlpha((int) (((((double) hsv.getScrollX() + 1d) / 71d) * 255d)));
                ((Button) findViewById(R.id.listactivity_arrow_right)).getBackground().setAlpha((int) ((1 - (((double) hsv.getScrollX() + 1d) / 71d) * 255d)));
            }
        });

        speech.setOnClickListener(new OnClickListener() {

            public void onClick(View arg0) {

                try {
                    Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
                    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
                    startActivityForResult(intent, VOICE_RECOGNITION_REQUEST_CODE);
                    progressD = ProgressDialog.show(ListActivity.this, getString(R.string.app_name), getString(R.string.busy), true, true);
                } catch (Exception ex) {
                    Log.e(TAG, "No SpeechInput " + ex);
                    showError(getString(R.string.nospeech));

                }

            }
        });

        sync.setOnClickListener(new OnClickListener() {

            public void onClick(View arg0) {

                new SynchronizeTask().execute();

            }
        });

        autogenerate.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {

                // fetch the amount of recipes from the preferences
                String rta = PreferenceManager.getDefaultSharedPreferences(ListActivity.this).getString(Variables.PREFERENCE_RECIPECOUNT, "5");
                int recipesToAdd = Integer.parseInt(rta);

                // let the generator generate
                generator.generate(recipesToAdd, true, true);

            }
        });

        add.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                // items or recipes quick action
                addQuickAction.show(add);
            }
        });

        clear.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                clearListQuestion();
            }
        });

        scan.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                try {
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
                    Intent intent = new Intent("com.google.zxing.client.android.SCAN");
                    startActivityForResult(intent, BARCODE_ACTIVITY_REQUEST_CODE);
                } catch (Exception e) {
                    showError(getString(R.string.noscanner));
                }
            }
        });


        list.setOnItemLongClickListener(new OnItemLongClickListener() {

            @Override
            public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
                long pos = list.getExpandableListPosition(arg2);
                if (ExpandableListView.getPackedPositionChild(pos) < 0) {
                    CategoryEditQuestion(ExpandableListView.getPackedPositionGroup(pos));
                } else {
                    itemEditQuestion((Item) myAdapter.getChild(ExpandableListView.getPackedPositionGroup(pos), ExpandableListView.getPackedPositionChild(pos)));
                }
                return false;
            }
        });

        list.setOnChildClickListener(new OnChildClickListener() {

            @Override
            public boolean onChildClick(ExpandableListView arg0, View arg1, int group, int child, long arg4) {
                ((Item) myAdapter.getChild(group, child)).bought = !((Item) myAdapter.getChild(group, child)).bought;
                myAdapter.notifyDataSetChanged();
                if (myAdapter.allChecked()) {
                    myAdapter.onAllChecked();
                }
                return false;
            }
        });

        updateGenerator();

    }

    private void CategoryEditQuestion(final int catId) {
        final String category = (String) myAdapter.getGroup(catId);

        AlertDialog.Builder dB = new AlertDialog.Builder(this);
        dB.setTitle(category.equals("") ? getString(R.string.uncategorized) : category);
        dB.setMessage(getString(R.string.editquestion));

        // edit the name of the category
        dB.setPositiveButton(getString(R.string.edit), new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int id) {

                AlertDialog.Builder alert = new AlertDialog.Builder(ListActivity.this);

                alert.setTitle(category.equals("") ? getString(R.string.uncategorized) : category);

                // Set an EditText view to get user input
                final EditText input = new EditText(ListActivity.this);
                alert.setView(input);
                input.setText(category);
                alert.setPositiveButton(getString(R.string.apply),
                        new DialogInterface.OnClickListener() {

                            public void onClick(DialogInterface dialog, int whichButton) {

                                String newCatName = input.getText().toString();
                                Category newCategory = null;

                                // no name entered
                                if (newCatName.replaceAll(" ", "").equals("")) {
                                    newCategory = new Category(Category.UNCATEGORIZED, getString(R.string.uncategorized), 0xaaaaaa);
                                }

                                // check if category already exists
                                ArrayList<Category> categories = adapter.getCategories();
                                for (Category cats : categories) {
                                    if (cats.name.equals(newCatName)) {
                                        newCategory = cats;
                                        break;
                                    }
                                }

                                // create new category and write to db
                                if (newCategory == null) {
                                    newCategory = new Category();
                                    newCategory.name = newCatName;
                                    adapter.addCategory(newCategory);
                                    newCategory.id = adapter.getCategoryIdByName(newCatName);
                                }

                                // update all items in that category
                                for (int i = 0; i < myAdapter.getChildrenCount(catId); i++) {
                                    Item item = (Item) myAdapter.getChild(catId, i);
                                    item.category = newCategory;
                                    adapter.updateMainItem(item);
                                }

                                initList();

                            }
                        });

                alert.setNegativeButton(getString(R.string.cancel), null);
                alert.show();
            }
        });
        dB.setNegativeButton(getString(R.string.delete), new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int id) {
                // remove all items in that category
                for (int i = 0; i < myAdapter.getChildrenCount(catId); i++) {
                    adapter.removeFromMainList(((Item) myAdapter.getChild(catId, i)).id);
                    initList();
                }
            }
        });
        dB.create().show();
    }

    private void initSpinner(final Spinner category, final Item editedItem) {
        // initialize category spinner
        final ArrayList<Category> categories = new ArrayList<Category>();
        ArrayAdapter<Category> stringAdapter = new ArrayAdapter<Category>(this, R.layout.simple_spinner_item, categories);
        // add uncategorized and new category to the list
        categories.add(new Category(Category.NEW_CATEGORY, getString(R.string.new_), 0xffff00));
        categories.add(new Category(Category.UNCATEGORIZED, getString(R.string.uncategorized), 0xaaaaaa));

        // initialize names of categories
        ArrayList<Category> cats = adapter.getCategories();
        for (Category cat : cats) {
            if (!cat.isRecipe) {
                categories.add(cat);
            }
        }

        category.setPrompt(getString(R.string.category));

        category.setAdapter(stringAdapter);

        category.setSelection(1);

        final int maxSize = categories.size();

        // check whether to create a new category
        category.setOnItemSelectedListener(new OnItemSelectedListener() {

            @Override
            public void onItemSelected(AdapterView<?> arg0, View arg1, int pos, long arg3) {

                Category selectedCategory = (Category) category.getItemAtPosition(pos);

                // new category was selected
                if (selectedCategory.id == Category.NEW_CATEGORY) {
                    AlertDialog.Builder alert = new AlertDialog.Builder(ListActivity.this);

                    // set selection to original category in case the user cancels the dialog
                    category.setSelection(1);

                    alert.setTitle(getString(R.string.new_category));
                    alert.setMessage(getString(R.string.name) + ":");

                    // Set an EditText view to get user input
                    final EditText input = new EditText(ListActivity.this);
                    alert.setView(input);
                    input.setText("");

                    // on apply:
                    alert.setPositiveButton(getString(R.string.apply),
                            new DialogInterface.OnClickListener() {

                                @Override
                                public void onClick(DialogInterface dialog, int whichButton) {

                                    // update new category
                                    String newCatName = input.getText().toString();

                                    // no name entered
                                    if (newCatName.replaceAll(" ", "").equals("")) {
                                        editedItem.category.id = Category.UNCATEGORIZED;
                                    }

                                    // check if category already exists
                                    boolean exists = false;
                                    for (Category cats : categories) {
                                        if (cats.name.equals(newCatName)) {
                                            itemCat = cats;
                                            exists = true;
                                            newCat = null;
                                            break;
                                        }
                                    }
                                    if (!exists) {

                                        // save the new category to be added to the category db later
                                        Category c = new Category();
                                        c.name = newCatName;
                                        newCat = c;
                                        itemCat = c;

                                        // remove the last entry if it is a "new" category that doesnt exist yet
                                        if (categories.size() > maxSize) {
                                            categories.remove(categories.size() - 1);
                                        }

                                        categories.add(c);

                                    }
                                    if (itemCat != null) {
                                        category.setSelection(categories.indexOf(itemCat));
                                    }
                                }
                            });

                    alert.setNegativeButton(getString(R.string.cancel), null);
                    alert.show();

                } else {
                    itemCat = selectedCategory;
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> arg0) {
                // set the entry to the items category
                for (int i = 0; i < category.getCount(); i++) {
                    Category selectedCategory = (Category) category.getItemAtPosition(i);
                    if (selectedCategory.id == editedItem.id) {
                        category.setSelection(i);
                    }
                }
            }
        });

        // set the entry to the items category
        for (int i = 0; i < category.getCount(); i++) {
            Category selectedCategory = (Category) category.getItemAtPosition(i);
            if (selectedCategory.id == editedItem.category.id) {
                category.setSelection(i);
                break;
            }
        }

    }

    private void itemEditQuestion(final Item item) {

        // start building the dialog
        AlertDialog.Builder dB = new AlertDialog.Builder(this);
        dB.setTitle(item.name);
        dB.setMessage(getString(R.string.editquestion));

        // edit amount or category
        dB.setPositiveButton(getString(R.string.edit), new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int id) {

                AlertDialog.Builder alert = new AlertDialog.Builder(ListActivity.this);

                alert.setTitle(item.name);
                alert.setMessage(getString(R.string.edititem));

                // get the layout
                LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
                View layout = inflater.inflate(R.layout.edit_dialog, null);

                alert.setView(layout);

                // Set an EditText view to get user input
                final EditText amount = (EditText) layout.findViewById(R.id.editdialog_edittext_amount);
                amount.setKeyListener(DigitsKeyListener.getInstance(false, true));
                // Set a spinner for the categories
                final Spinner categorySelect = (Spinner) layout.findViewById(R.id.editdialog_spinner_categories);

                initSpinner(categorySelect, item);

                amount.setText("" + item.amount);
                // on apply:
                alert.setPositiveButton(getString(R.string.apply),
                        new DialogInterface.OnClickListener() {

                            public void onClick(DialogInterface dialog, int whichButton) {

                                // update amount
                                String newAmount = amount.getText().toString();
                                item.price *= Double.parseDouble(newAmount)
                                        / item.amount;
                                item.amount = Double.parseDouble(newAmount);

                                if (newCat != null) {
                                    adapter.addCategory(newCat);
                                    Log.d(TAG, "Category added!!!");
                                    itemCat = adapter.getCategoryByName(newCat.name);
                                }
                                if (itemCat != null) {
                                    item.category = itemCat;
                                }

                                adapter.updateMainItem(item);
                                //myAdapter.notifyDataSetChanged();
                                initList();
                            }
                        });

                alert.setNegativeButton(getString(R.string.cancel), null);
                alert.show();
            }
        });

        // delete Item
        dB.setNegativeButton(getString(R.string.delete), new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int id) {
                adapter.removeFromMainList(item.id);
                infos.remove(item);
                // refresh list if a whole category was deleted by removing one item
                if (!myAdapter.remove(item)) {
                    initList();
                }
                myAdapter.notifyDataSetChanged();
            }
        });
        AlertDialog d = dB.create();
        d.show();
    }

    private void initDB() {
        adapter = new DBAdapter(this);
        adapter.open();
    }

    /**
     * compares two Strings and returns the similarity (0 = not similar, 1 = same)
     * @author http://www.codeproject.com/KB/java/JavaFuzzyStringMatch.aspx
     */
    private double similarity(String string1, String string2) {
        //turn first string into a bigram
        ArrayList<char[]> bigram1 = new ArrayList<char[]>();
        for (int i = 0; i < string1.length() - 1; i++) {
            char[] chars = new char[2];
            chars[0] = string1.charAt(i);
            chars[1] = string1.charAt(i + 1);
            bigram1.add(chars);
        }
        //turn second string into a bigram
        ArrayList<char[]> bigram2 = new ArrayList<char[]>();
        for (int i = 0; i < string2.length() - 1; i++) {
            char[] chars = new char[2];
            chars[0] = string2.charAt(i);
            chars[1] = string2.charAt(i + 1);
            bigram2.add(chars);
        }
        //compare them
        List<char[]> copy = new ArrayList<char[]>(bigram2);
        int matches = 0;
        for (int i = bigram1.size(); --i >= 0;) {
            char[] bigram = bigram1.get(i);
            for (int j = copy.size(); --j >= 0;) {
                char[] toMatch = copy.get(j);
                if (bigram[0] == toMatch[0] && bigram[1] == toMatch[1]) {
                    copy.remove(j);
                    matches += 2;
                    break;
                }
            }
        }
        return (double) matches / (bigram1.size() + bigram2.size());
    }

    private void initList() {
        if (infos == null) {
            infos = new ArrayList<Item>();
        }
        LoadItemsTask lt = new LoadItemsTask();
        lt.execute(0);
    }

    /**
     * shows an error dialog with only one ok button
     * @param message the displayed text
     */
    private void showError(String message) {

        AlertDialog.Builder dB = new AlertDialog.Builder(this);
        dB.setTitle(getString(R.string.error));
        dB.setMessage(message);
        dB.setPositiveButton(getString(R.string.ok), null);
        AlertDialog d = dB.create();
        d.show();

    }

    private void clearListQuestion() {
        AlertDialog.Builder dB = new AlertDialog.Builder(this);
        dB.setTitle(getString(R.string.clear));
        dB.setMessage(getString(R.string.clearlist));
        dB.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int id) {
                adapter.clearMainList();
                infos.clear();
                myAdapter.notifyDataSetChanged();
                initList();
                checkClearButton();
            }
        });
        dB.setNegativeButton(getString(R.string.no), null);
        AlertDialog d = dB.create();
        d.show();
    }

    private void allBoughtMessage() {
        AlertDialog.Builder dB = new AlertDialog.Builder(this);
        dB.setTitle(getString(R.string.allbought));
        dB.setMessage(getString(R.string.clearafterbought));
        dB.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int id) {
                for (int i = 0; i < infos.size(); i++) {
                    if (infos.get(i).boughtcount == 0) { // dont change the period if the item wasnt bought yet
                        infos.get(i).avgperiod = 0;
                    } else {
                        if (infos.get(i).boughtcount == 1) { // create a new period if the item was bought the second time now
                            infos.get(i).avgperiod = ((System.currentTimeMillis() - infos.get(i).lastbought) / 86400000);

                        } else { // change the average period by adding or removing the difference / boughtcount
                            infos.get(i).avgperiod += (((System.currentTimeMillis() - infos.get(i).lastbought) / 86400000) - infos.get(i).avgperiod) / infos.get(i).boughtcount;
                        }
                    }
                    // update the item
                    infos.get(i).boughtcount++;
                    infos.get(i).lastbought = System.currentTimeMillis();
                    adapter.addItemToHistory(infos.get(i));
                    adapter.onItemBought(infos.get(i));
                }

                // re initialize the list
                adapter.clearMainList();
                infos.clear();
                myAdapter.notifyDataSetChanged();
                updateGenerator();
                initList();
            }
        });
        dB.setNegativeButton(getString(R.string.no), null);
        AlertDialog d = dB.create();
        d.show();
    }

    public void onConnectionStatusChange(int status) {
        switch (status) {
            case ConnectionStatusListener.CONNECTION_CONNECTED:
                Toast.makeText(this, R.string.sync_connected, Toast.LENGTH_SHORT).show();
                startActivity(new Intent(ListActivity.this, SyncActivity.class));
                break;
            case ConnectionStatusListener.CONNECTION_TIMEOUT:
                // init the dialog
                AlertDialog.Builder builder = new AlertDialog.Builder(ListActivity.this);
                builder.setTitle(getString(R.string.sync_timeout));
                builder.setMessage(R.string.sync_timeout_info);
                builder.setPositiveButton(getString(R.string.ok), null);
                builder.show();
                break;
            case ConnectionStatusListener.CONNECTION_WRONGVERSION:
                // init the dialog
                AlertDialog.Builder builder2 = new AlertDialog.Builder(ListActivity.this);
                builder2.setTitle(getString(R.string.sync_wrongversion));
                builder2.setMessage(getString(R.string.sync_wrongversion_info));
                builder2.setPositiveButton(getString(R.string.ok), null);
                builder2.show();
                break;
        }
    }

    /**
     * async task to download product information with a given barcode (Long)
     * implements a ProgressListener so that the BarcooDBAdapter can send progress to this task so that it can be shown
     */
    private class DownloadInfoTask extends AsyncTask<Long, Integer, Integer> implements
            ProgressListener {

        ProgressDialog progressDialog;
        Item onlineItem;

        @Override
        protected void onPreExecute() {
            // initialize the progress dialog and setup the progresslistener
            progressDialog = new ProgressDialog(ListActivity.this);
            progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            progressDialog.setTitle(R.string.retrieveinfo_title);
            progressDialog.setMessage(ListActivity.this.getString(R.string.retrieveinfo_msg));
            progressDialog.setCancelable(false);
            progressDialog.show();
            BarcooDBAdapter.addProgressListener(this);
        }

        @Override
        protected Integer doInBackground(Long... barcode) {

            // get item, BarcooDBAdapter is doing all the work
            onlineItem = BarcooDBAdapter.getItem(barcode[0]);
            return null;

        }

        @Override
        protected void onPostExecute(Integer result) {

            BarcooDBAdapter.removeProgressListener(this);

            progressDialog.dismiss();

            if (onlineItem != null) {
                // if an item was found save it to the database and re init the list
                if (onlineItem.id != BarcooDBAdapter.NOTFOUNDID) {
                    Log.i(TAG, "Downloaded new item: " + onlineItem.name + " (" + onlineItem.id
                            + ")");
                    onlineItem.amount = 1;
                    onlineItem.unit = 'p';
                    adapter.addItem(onlineItem);
                    adapter.addToMainList(adapter.getItemByBarcode(onlineItem.barcode));
                    infos.add(onlineItem);
                    initList();
                    myAdapter.notifyDataSetChanged();
                } else {
                    showError(getString(R.string.itemnotfound));
                }
            } else {
                showError(getString(R.string.checkinternet));
            }
        }

        @Override
        public void onProgressUpdate(int progress) {

            progressDialog.setProgress(progress);

        }
    }

    /**
     * the main class used to generate an automatic list
     */
    private class ListGenerator {

        // private int recipesAdded;
        private int recipesToAdd;
        private boolean canRecipes = false;
        private boolean canItems = false;
        private ArrayList<Item> neededItems;

        private ListGenerator() {
            check();
        }

        /**
         * update the can* variables and the neededItems (that need to be bought)
         */
        private void check() {
            if (adapter.getRecipeCount() > 0) {
                canRecipes = true;
            } else {
                canRecipes = false;
            }
            if (adapter.getGenerateableItemsCount() > 0) {
                canItems = true;
            } else {
                canItems = false;
            }
        }

        public boolean canGenerate() {
            return (canRecipes || canItems);
        }

        public void showDialogs(ArrayList<Recipe> recs, ArrayList<Item> items) {
            if (recs.size() > 0) {
                addRecipeQuestion(recs, 0, 0);
            }
            if (items.size() > 0) {
                addItemListQuestion(items, QUESTION_MODE_LASTBOUGHT);
            }
        }

        /**
         * Generates a shopping list depending on what you havent bought for some time with options to add recipes or items
         * @param recipesToAdd how many recipes one wants to add to the shopping list (0 = dont add recipes)
         * @param genItems whether to add items to the list
         * @param question whether to show dialogs what to add
         */
        public void generate(int recipesToAdd, boolean genItems, boolean question) {

            this.recipesToAdd = recipesToAdd;
            // start the generator in the background
            new GeneratorTask(recipesToAdd > 0, genItems, question).execute();

        }

        /**
         * shows a list dialog to choose which items of the automatically suggested ones to actually add
         * @param list the items youd possibly want to add
         */
        public void addItemListQuestion(final ArrayList<Item> list, int mode) {
            // names of the items and their last bought info
            final String[] strings = new String[list.size()];
            // set all checkboxes to true and get the name and lastbought info for all items
            for (int i = 0; i < list.size(); i++) {
                switch (mode) {
                    case (QUESTION_MODE_LASTBOUGHT):
                        String days = getString(R.string.lastbought).replace("$n", "" + (int) ((System.currentTimeMillis() - list.get(i).lastbought) / 86400000));
                        if ((int) ((System.currentTimeMillis() - list.get(i).lastbought) / 86400000) > 365) {
                            days = getString(R.string.never);
                        }
                        strings[i] = list.get(i).name + "\n" + days;
                        break;
                    case (QUESTION_MODE_AMOUNT):
                        String theUnit = getString(R.string.unit_pc);
                        switch (list.get(i).unit) {
                            case ('k'):
                                theUnit = getString(R.string.unit_kg);
                                break;
                            case ('g'):
                                theUnit = getString(R.string.unit_g);
                                break;
                            case ('l'):
                                theUnit = getString(R.string.unit_l);
                                break;
                            case ('m'):
                                theUnit = getString(R.string.unit_ml);
                                break;
                        }
                        strings[i] = list.get(i).name + "\n" + list.get(i).amount + theUnit;
                        break;
                }
            }

            // init the dialog
            AlertDialog.Builder builder = new AlertDialog.Builder(ListActivity.this);
            builder.setTitle(getString(R.string.suggest));
            builder.setMultiChoiceItems(strings, null, null);

            builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    for (int i = 0; i < strings.length; i++) {
                        // add still checked items to the mainlist
                        if (((AlertDialog) dialog).getListView().getCheckedItemPositions().get(i)) {
                            adapter.addToMainList(list.get(i));
                            infos.add(list.get(i));
                        }
                    }
                    initList();
                    myAdapter.notifyDataSetChanged();
                }
            });
            builder.setNegativeButton(getString(R.string.cancel), null);
            builder.show();
        }

        /**
         * shows a question whether to add a recipe
         * @param recipes the list containing all recipes
         * @param nextindex the index saying which recipe of the list to add
         */
        private void addRecipeQuestion(final ArrayList<Recipe> recipes, final int nextindex, final int recipesAdded) {

            AlertDialog.Builder dB = new AlertDialog.Builder(ListActivity.this);

            // initialize the custom layout
            LayoutInflater inflater = (LayoutInflater) getApplicationContext().getSystemService(LAYOUT_INFLATER_SERVICE);
            View layout = inflater.inflate(R.layout.rating_dialog, null);
            RatingBar rbar = (RatingBar) layout.findViewById(R.id.rating_dialog_ratingbar);
            rbar.setRating((float) recipes.get(nextindex).rating);

            dB.setView(layout);
            dB.setTitle(ListActivity.this.getString(R.string.addrecipe_title).replace("$n",
                    recipes.get(nextindex).name));
            dB.setMessage(ListActivity.this.getString(R.string.addrecipe_msg).replace("$n", recipes.get(nextindex).name));
            dB.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {

                public void onClick(DialogInterface dialog, int id) {
                    // show another addRecipeQuestion as long as there are recipes to add
                    // add a recipe to the list
                    new InsertRecipeTask().execute(recipes.get(nextindex).id);

                    if (recipesAdded < recipesToAdd - 1) {
                        if (nextindex != recipes.size() - 1) {
                            addRecipeQuestion(recipes, nextindex + 1, recipesAdded + 1);
                        }
                    }
                }
            });

            dB.setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() {

                public void onClick(DialogInterface dialog, int id) {
                    // show another addRecipeQuestion as long as there are recipes to add
                    if (nextindex != recipes.size() - 1) {
                        addRecipeQuestion(recipes, nextindex + 1, recipesAdded);
                    }

                }
            });

            AlertDialog d = dB.create();
            d.show();
        }

        /**
         * async task to insert a new recipe into the list
         */
        private class GeneratorTask extends AsyncTask<Integer, Integer, Integer> {

            private ProgressDialog pd;
            private boolean canceled = false;
            ArrayList<Recipe> recs = new ArrayList<Recipe>();
            ArrayList<Item> items = new ArrayList<Item>();
            private boolean showDialogs;
            private boolean genRecipes;
            private boolean genItems;

            public GeneratorTask(boolean genRecipes, boolean genItems, boolean showDialgos) {
                this.showDialogs = showDialgos;
                this.genRecipes = genRecipes;
                this.genItems = genItems;
            }

            @Override
            protected void onPreExecute() {
                if (!isFinishing()) {
                    pd = ProgressDialog.show(ListActivity.this, getString(R.string.app_name), getString(R.string.generatorbusy), true, true);
                    pd.setOnCancelListener(new OnCancelListener() {

                        public void onCancel(DialogInterface arg0) {
                            canceled = true;
                        }
                    });

                    check();
                } else {
                    canceled = true;
                }
            }

            @Override
            protected Integer doInBackground(Integer... arg) {
                ArrayList<Integer> ids;

                ids = adapter.getRecipeIDs();

                // fetch recipes                
                if (genRecipes && canRecipes) {
                    for (int i = 0; i < ids.size(); i++) {
                        if (canceled == false) {
                            recs.add(adapter.getRecipeById(ids.get(i)));
                        } else {
                            return null;
                        }
                    }
                }

                // fetch items              
                if (genItems && canItems) {
                    ids = adapter.getGenerateableItemIDs();
                    for (int i = 0; i < ids.size(); i++) {
                        if (canceled == false) {
                            items.add(adapter.getItemById(ids.get(i)));
                        } else {
                            return null;
                        }
                    }
                }

                return null;
            }

            @Override
            protected void onPostExecute(Integer result) {

                if (!canceled) {

                    Recipe.setSortMode(Recipe.SORT_GENERATOR);
                    Collections.sort(recs);

                    if (showDialogs) {
                        pd.dismiss();
                        showDialogs(recs, items);
                    } else {
                        if (genRecipes) {
                            Log.d(TAG, "generating recipes:");
                            if (recipesToAdd > recs.size()) {
                                recipesToAdd = recs.size();
                            }
                            Log.d(TAG, "generated recipes: " + recipesToAdd);
                            for (int r = 0; r < recipesToAdd; r++) {

                                Log.d(TAG, "adding: " + recs.get(r).name);

                                // Update the recipe in the database
                                recs.get(r).lastBought = System.currentTimeMillis();
                                adapter.updateRecipe(recs.get(r));

                                // Pick all items of the recipe (shadowing items)
                                ArrayList<Item> items = recs.get(r).ingredients;

                                // Put all items into the list
                                for (int i = 0; i < items.size(); i++) {
                                    adapter.addToMainList(items.get(i));
                                    infos.add(items.get(i));
                                    Log.d(TAG, "added " + items.get(i).name);
                                }

                            }
                        }
                        if (genItems) {
                            for (int i = 0; i < items.size(); i++) {
                                adapter.addToMainList(items.get(i));
                                infos.add(items.get(i));
                            }
                        }
                        pd.dismiss();
                    }

                    initList();
                    myAdapter.notifyDataSetChanged();

                }

            }
        }
    }

    private void updateLayout() {
        createUI();
    }

    /**
     * async task to insert all mainlist items from the database into the list
     */
    private class LoadItemsTask extends AsyncTask<Integer, Integer, Integer> {

        private ProgressDialog pd;

        @Override
        protected void onPreExecute() {
            infos.clear();
            pd = ProgressDialog.show(ListActivity.this, getString(R.string.app_name), getString(R.string.loading), true, false);
            Log.v(TAG, "started the LoadItemsTask");
        }

        @Override
        protected Integer doInBackground(Integer... arg) {
            infos = adapter.getAllMainListItems();
            Log.v(TAG, "finished reading items from db");
            return null;
        }

        @Override
        protected void onPostExecute(Integer result) {
            // reinitialize the adapter
            myAdapter = new MainExpandableListAdapter(ListActivity.this, R.layout.item, infos, adapter) {

                @Override
                public void onDataSetChange() {
                    totalPrice = 0;
                    for (int i = 0; i < infos.size(); i++) {
                        totalPrice += infos.get(i).price;
                    }
                    updatePrice();
                    checkClearButton();
                    updateLayout();
                }

                @Override
                public void onAllChecked() {
                    allBoughtMessage();
                }
            };
            updateLayout();
            list.setAdapter(myAdapter);
            myAdapter.setMode(MainExpandableListAdapter.ICONS, true);
            myAdapter.setMode(MainExpandableListAdapter.PRICE, PreferenceManager.getDefaultSharedPreferences(ListActivity.this).getBoolean(Variables.PREFERENCE_SHOWPRICE, true));
            myAdapter.setMode(MainExpandableListAdapter.CHECKBOX, true);

            checkClearButton();

            // needs to be called here, because this needs to be called when the task has finished loading
            if (juststarted) {
                juststarted = false;
                autogenerate();
            }
            totalPrice = 0;
            for (int i = 0; i < infos.size(); i++) {
                totalPrice += infos.get(i).price;
            }
            try {
                pd.dismiss();
            } catch (Exception e) {
                Log.e(TAG, "Error dismissing the Progress Dialog: " + e.getMessage());
            }

            Log.v(TAG, "done loading the main list");
            updatePrice();

        }
    }

    /**
     * async task to insert a new recipe into the list
     */
    private class InsertRecipeTask extends AsyncTask<Integer, Integer, Integer> {

        private ProgressDialog pd;

        @Override
        protected void onPreExecute() {
            pd = ProgressDialog.show(ListActivity.this, getString(R.string.app_name), getString(R.string.busy), true, false);
        }

        @Override
        protected Integer doInBackground(Integer... arg) {
            Recipe recipe = adapter.getRecipeById(arg[0]);

            recipe.lastBought = System.currentTimeMillis();
            adapter.updateRecipe(recipe);

            for (int i = 0; i < recipe.ingredients.size(); i++) {
                Item ingredient = recipe.ingredients.get(i);
                adapter.addToMainList(ingredient);
            }
            return null;
        }

        @Override
        protected void onPostExecute(Integer result) {
            pd.dismiss();
            initList();
        }
    }

    /**
     * async task to insert a new recipe into the list
     */
    private class SynchronizeTask extends AsyncTask<Integer, Integer, Integer> {

        public static final String TAG = "SynchronizeTask"; // shadowing TAG        
        public static final int TIMEOUT = 10;
        public static final int CONNECTION_DISCONNECTED = 0;
        public static final int CONNECTION_CONNECTING = 1;
        public static final int CONNECTION_TIMEOUT = 2;
        public static final int CONNECTION_CONNECTED = 3;
        public static final int CONNECTION_WRONGVERSION = 4;
        private int connectionStatus = CONNECTION_DISCONNECTED;
        private ServerSocket server;
        private Socket client;
        private ProgressDialog pd;
        // data streams
        private PrintWriter out;
        private BufferedReader in;
        private ArrayList<ConnectionStatusListener> listeners = new ArrayList<ConnectionStatusListener>();
        private int statusDuringExecution = CONNECTION_DISCONNECTED;

        public void addConnectionStatusListener(ConnectionStatusListener listener) {
            listeners.add(listener);
        }

        public void removeConnectionStatusListener(ConnectionStatusListener listener) {
            listeners.remove(listener);
        }

        @Override
        protected void onPreExecute() {
            pd = ProgressDialog.show(ListActivity.this, getString(R.string.sync_subtitle), getString(R.string.sync_connecting), true, false);
        }

        @Override
        protected Integer doInBackground(Integer... arg) {

            try {

                server = new ServerSocket(Variables.PORT_ADBUSBPORT);
                server.setSoTimeout(TIMEOUT * 1000);
                // setConnectionStatus(CONNECTION_CONNECTING);

                client = server.accept();
                in = new BufferedReader(new InputStreamReader(client.getInputStream()));
                out = new PrintWriter(client.getOutputStream(), true);

            } catch (SocketTimeoutException e) {
                statusDuringExecution = CONNECTION_TIMEOUT;
            } catch (IOException e) {
                statusDuringExecution = CONNECTION_DISCONNECTED;
            } finally {

                //close the server socket
                try {
                    if (server != null) {
                        server.close();
                    }
                } catch (IOException ec) {
                    Log.e(TAG, "Cannot close server socket" + ec);
                }

            }

            if (client != null) {

                if (client.isConnected()) {
                    SyncAdapter.initAdapter(client, in, out, adapter);
                    if (SyncAdapter.getInstance().isConnected()) {
                        statusDuringExecution = CONNECTION_CONNECTED;
                    } else {
                        statusDuringExecution = CONNECTION_WRONGVERSION;
                    }
                }

            }

            return null;
        }

        @Override
        protected void onPostExecute(Integer result) {
            setConnectionStatus(statusDuringExecution);
            pd.dismiss();
        }

        public void setConnectionStatus(int status) {
            connectionStatus = status;
            notifyListeners();
        }

        private void notifyListeners() {
            for (ConnectionStatusListener listener : listeners) {
                listener.onConnectionStatusChange(connectionStatus);
            }
        }
    }
}
